﻿using System;
using System.Collections.Generic;
using System.Linq;
using Jira.JiraSoap;
using Utilities;

namespace Jira
{
    /// <summary>
    /// See http://docs.atlassian.com/software/jira/docs/api/rpc-jira-plugin/latest/com/atlassian/jira/rpc/soap/JiraSoapService.html
    /// for documentation of the SOAP API.
    /// See http://confluence.atlassian.com/display/JIRA/Advanced+Searching
    /// for documentation of JQL searches.
    /// </summary>
    public sealed class JiraConnection : DisposableBase
    {
        private readonly JiraSoapServiceClient m_Client;
        private readonly string m_UserName;
        private readonly string m_Token;

        private readonly IDictionary<string, string> m_IssueTypeNameToId;
        private readonly IDictionary<string, string> m_StatusIdToName;
        private readonly IDictionary<string, string> m_ResolutionIdToName;
        private readonly IDictionary<string, string> m_ResolutionNameToId;
        private readonly IDictionary<string, string> m_CustomFieldNameToId = new Dictionary<string, string>();
        private readonly IDictionary<string, string> m_ActionNameToId = new Dictionary<string, string>();


        public JiraConnection(string userName, string password)
        {
            m_Client = new JiraSoapServiceClient();
            m_UserName = userName;
            m_Token = m_Client.login(userName, password);

            RemoteIssueType[] issueTypes = m_Client.getIssueTypes(m_Token);
            m_IssueTypeNameToId = issueTypes.ToDictionary(x => x.name, x => x.id, StringComparer.InvariantCultureIgnoreCase);

            RemoteStatus[] statuses = m_Client.getStatuses(m_Token);
            m_StatusIdToName = statuses.ToDictionary(x => x.id, x => x.name, StringComparer.InvariantCultureIgnoreCase);

            RemoteResolution[] resolutions = m_Client.getResolutions(m_Token);
            m_ResolutionIdToName = resolutions.ToDictionary(x => x.id, x => x.name, StringComparer.InvariantCultureIgnoreCase);
            m_ResolutionNameToId = resolutions.ToDictionary(x => x.name, x => x.id, StringComparer.InvariantCultureIgnoreCase);
        }

        protected override void Dispose(bool disposing)
        {
            if (!IsDisposed)
            {
                if (disposing)
                {
                    try
                    {
                        m_Client.logout(m_Token);
                    }
                    finally
                    {
                        ((IDisposable) m_Client).Dispose();
                    }
                }
            }

            base.Dispose(disposing);
        }

        internal JiraSoapServiceClient Client
        {
            get
            {
                CheckDisposed();
                return m_Client;
            }
        }

        internal string Token
        {
            get
            {
                CheckDisposed();
                return m_Token;
            }
        }

        internal string GetIssueTypeId(string name)
        {
            CheckDisposed();
            return m_IssueTypeNameToId[name];
        }

        internal string GetCustomFieldId(RemoteIssue remoteIssue, string customFieldName)
        {
            CheckDisposed();

            string customFieldId;
            if (!m_CustomFieldNameToId.TryGetValue(customFieldName, out customFieldId))
            {
                RemoteField[] fields = m_Client.getFieldsForEdit(m_Token, remoteIssue.key);
                if (fields != null)
                {
                    foreach (RemoteField field in fields)
                    {
                        // merge over the top if it's already there
                        m_CustomFieldNameToId[field.name] = field.id;
                    }
                }

                if (!m_CustomFieldNameToId.TryGetValue(customFieldName, out customFieldId))
                {
                    throw new Exception(String.Format("Could not find field {0} for issue {1}. Check that the field exists and that the user {2} has permission to access it", customFieldName, remoteIssue.key, m_UserName));
                }
            }

            return customFieldId;
        }

        internal string GetActionId(RemoteIssue remoteIssue, string actionName)
        {
            CheckDisposed();

            string actionId;
            if (!m_ActionNameToId.TryGetValue(actionName, out actionId))
            {
                RemoteNamedObject[] namedObjects = m_Client.getAvailableActions(m_Token, remoteIssue.key);
                if (namedObjects != null)
                {
                    foreach (RemoteNamedObject namedObject in namedObjects)
                    {
                        // merge over the top if it's already there
                        m_ActionNameToId[namedObject.name] = namedObject.id;
                    }
                }

                if (!m_ActionNameToId.TryGetValue(actionName, out actionId))
                {
                    throw new Exception(String.Format("Could not find action {0} for issue {1}. Check that the action exists and that the user {2} has permission to perform it", actionName, remoteIssue.key, m_UserName));
                }
            }

            return actionId;
        }

        internal string GetStatusName(string id)
        {
            CheckDisposed();
            return m_StatusIdToName[id];
        }

        internal string GetResolutionName(string id)
        {
            CheckDisposed();
            return m_ResolutionIdToName[id];
        }

        internal string GetResolutionId(string name)
        {
            CheckDisposed();
            return m_ResolutionNameToId[name];
        }
    }
}
